﻿/* 
* Copyright (c) 2010, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using Bio.Data.Interfaces;
using Bio.Views.Structure.Editor.Internal.Editing;
using Bio.Views.Structure.Editor.Internal.Rendering.Interfaces;
using Bio.Views.Structure.Editor.Internal.Rendering.Primitives;

namespace Bio.Views.Structure.Editor.Internal.Rendering.Visuals
{
    public class LoopStructureElementVisualComponent : DependencyObject, IStructureElementVisualComponent
    {
        #region Dependency Properties

        public static readonly DependencyProperty NucleotideFontFamilyProperty = DependencyProperty.Register("NucleotideFontFamily",
            typeof(FontFamily), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFontWeightProperty = DependencyProperty.Register("NucleotideFontWeight",
            typeof(FontWeight?), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFontStyleProperty = DependencyProperty.Register("NucleotideFontStyle",
            typeof(FontStyle?), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFontSizeProperty = DependencyProperty.Register("NucleotideFontSize",
            typeof(double?), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideRenderingStyleProperty = DependencyProperty.Register("NucleotideRenderingStyle",
            typeof(NucleotideRenderingMode?), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFillProperty = DependencyProperty.Register("NucleotideFill",
            typeof(Brush), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty IsNucleotideSubstitutedProperty = DependencyProperty.Register("IsNucleotideSubstituted",
           typeof(bool?), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideSubstitutionProperty = DependencyProperty.Register("NucleotideSubstitution",
            typeof(char?), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleDiameterProperty = DependencyProperty.Register("NucleotideCircleDiameter",
            typeof(double?), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleEdgeThicknessProperty = DependencyProperty.Register("NucleotideCircleEdgeThickness",
            typeof(double?), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleIsFilledProperty = DependencyProperty.Register("NucleotideCircleIsFilled",
            typeof(bool?), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleFillProperty = DependencyProperty.Register("NucleotideCircleFill",
            typeof(Brush), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleStrokeProperty = DependencyProperty.Register("NucleotideCircleStroke",
            typeof(Brush), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        private static readonly DependencyPropertyKey StrandTypePropertyKey = DependencyProperty.RegisterReadOnly("StrandType",
            typeof(StructureExtentType), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty StrandTypeProperty = StrandTypePropertyKey.DependencyProperty;

        public StructureExtentType StrandType 
        {
            get { return (StructureExtentType)GetValue(StrandTypeProperty); }
            private set { SetValue(StrandTypePropertyKey, value); }
        }

        private static readonly DependencyPropertyKey StrandPropertyKey = DependencyProperty.RegisterReadOnly("Strand",
            typeof(IEnumerable<IBioSymbol>), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty StrandProperty = StrandPropertyKey.DependencyProperty;

        public IEnumerable<IBioSymbol> Strand
        {
            get { return (IEnumerable<IBioSymbol>)GetValue(StrandProperty); }
            private set { SetValue(StrandPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey StartNucleotideIndexPropertyKey = DependencyProperty.RegisterReadOnly("StartNucleotideIndex",
            typeof(int), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty StartNucleotideIndexProperty = StartNucleotideIndexPropertyKey.DependencyProperty;

        public int StartNucleotideIndex
        {
            get { return (int)GetValue(StartNucleotideIndexProperty); }
            private set { SetValue(StartNucleotideIndexPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey EndNucleotideIndexPropertyKey = DependencyProperty.RegisterReadOnly("EndNucleotideIndex",
            typeof(int), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty EndNucleotideIndexProperty = EndNucleotideIndexPropertyKey.DependencyProperty;

        public int EndNucleotideIndex
        {
            get { return (int)GetValue(EndNucleotideIndexProperty); }
            private set { SetValue(EndNucleotideIndexPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey ContextMessagePropertyKey = DependencyProperty.RegisterReadOnly("ContextMessage",
            typeof(string), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(string.Empty));
        public static readonly DependencyProperty ContextMessageProperty = ContextMessagePropertyKey.DependencyProperty;

        public string ContextMessage
        {
            get { return (string)GetValue(ContextMessageProperty); }
            private set { SetValue(ContextMessagePropertyKey, value); }
        }

        private static readonly DependencyPropertyKey LocationPropertyKey = DependencyProperty.RegisterReadOnly("Location",
            typeof(Point), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(OnRenderingPropertyChanged));
        public static readonly DependencyProperty LocationProperty = LocationPropertyKey.DependencyProperty;

        public Point Location
        {
            get { return (Point)GetValue(LocationProperty); }
            private set { SetValue(LocationPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey ArcRadiusPropertyKey = DependencyProperty.RegisterReadOnly("ArcRadius",
            typeof(double), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(OnRenderingPropertyChanged));
        public static readonly DependencyProperty ArcRadiusProperty = ArcRadiusPropertyKey.DependencyProperty;

        public double ArcRadius
        {
            get { return (double)GetValue(ArcRadiusProperty); }
            private set { SetValue(ArcRadiusPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey ArcHeightPropertyKey = DependencyProperty.RegisterReadOnly("ArcHeight",
            typeof(double), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(OnRenderingPropertyChanged));
        public static readonly DependencyProperty ArcHeightProperty = ArcHeightPropertyKey.DependencyProperty;

        public double ArcHeight
        {
            get { return (double)GetValue(ArcHeightProperty); }
            private set { SetValue(ArcHeightPropertyKey, value); }
        }

        public static readonly DependencyProperty ArcStrokeProperty = DependencyProperty.Register("ArcStroke",
            typeof(Brush), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.LightBlue), FrameworkPropertyMetadataOptions.None, OnArcVisualPropertyChanged));

        public Brush ArcStroke
        {
            get { return (Brush)GetValue(ArcStrokeProperty); }
            set { SetValue(ArcStrokeProperty, value); }
        }

        public static readonly DependencyProperty ArcStrokeThicknessProperty = DependencyProperty.Register("ArcStrokeThickness",
            typeof(double), typeof(LoopStructureElementVisualComponent), new FrameworkPropertyMetadata(1.5, FrameworkPropertyMetadataOptions.None, OnArcVisualPropertyChanged));

        public double ArcStrokeThickness
        {
            get { return (double)GetValue(ArcStrokeThicknessProperty); }
            set { SetValue(ArcStrokeThicknessProperty, value); }
        }

        private static void OnComponentVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            LoopStructureElementVisualComponent loopVisual = d as LoopStructureElementVisualComponent;
            if (loopVisual != null)
            {
                loopVisual.PropagateVisualPropertyChanged();
            }
        }

        private static void OnArcVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        }

        private static void OnRenderingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            LoopStructureElementVisualComponent loopVisual = d as LoopStructureElementVisualComponent;
            if (loopVisual != null)
            {
                if (e.Property == LocationProperty ||
                    e.Property == ArcRadiusProperty ||
                    e.Property == ArcHeightProperty)
                {
                    loopVisual.PhysicalLoopVisual.Measure();
                    loopVisual.PhysicalLoopVisual.Render();
                }
            }
        }

        #endregion

        #region Properties

        public Rect Bounds
        {
            get;
            private set;
        }

        public bool IsSelected
        {
            get { return _isSelected; }
            private set
            {
                if (!IsSelectable && value)
                    return;
                _isSelected = value;
                PhysicalLoopVisual.Render();
            }
        }

        public IEnumerable<int> NucleotideIndices
        {
            get
            {
                return Enumerable.Range(StartNucleotideIndex, ((EndNucleotideIndex - StartNucleotideIndex) + 1));
            }
        }

        public List<Point> Edges
        {
            get { return _edges; }
        }

        #endregion

        #region IStructureElementVisualComponent Members

        public Visual MakeVisualSnapshot()
        {
            //Since this visual copy is basically for printing and screen captures, we return null
            //becuase a loop has no rendering components except when it is selected. In contrast
            //a base pair component has a connector and a nucleotide component has the nucleotide representation (text, circle, rectangle).
            return null;
        }

        public Visual VisualComponent
        {
            get { return PhysicalLoopVisual; }
        }

        public bool IsComponentVisible
        {
            get { return IsSelected; }
            set { }
        }

        private IStructureElementVisualComponent _parentComponent;
        public IStructureElementVisualComponent ParentComponent
        {
            get { return _parentComponent; }
            set
            {
                _parentComponent = value;
                NotifyParentHierarchyChanged();
            }
        }

        public IEnumerable<IStructureElementVisualComponent> Children
        {
            get { return ChildrenInternal.AsEnumerable<IStructureElementVisualComponent>(); }
        }

        public FontFamily NucleotideFontFamily
        {
            get
            {
                FontFamily val = GetValue(NucleotideFontFamilyProperty) as FontFamily;
                if (val != null)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFontFamily;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideFontFamilyProperty, value); }
        }

        public FontWeight? NucleotideFontWeight
        {
            get
            {
                FontWeight? val = (FontWeight?)GetValue(NucleotideFontWeightProperty);
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFontWeight;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideFontWeightProperty, value); }
        }

        public FontStyle? NucleotideFontStyle
        {
            get
            {
                FontStyle? val = (FontStyle?)GetValue(NucleotideFontStyleProperty);
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFontStyle;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideFontStyleProperty, value); }
        }

        public double? NucleotideFontSize
        {
            get
            {
                double? val = (double?)GetValue(NucleotideFontSizeProperty);
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFontSize;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideFontSizeProperty, value); }
        }

        public NucleotideRenderingMode? NucleotideRenderingStyle
        {
            get
            {
                NucleotideRenderingMode? val = (NucleotideRenderingMode?)GetValue(NucleotideRenderingStyleProperty);
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideRenderingStyle;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideRenderingStyleProperty, value); }
        }

        public Brush NucleotideFill
        {
            get
            {
                Brush val = GetValue(NucleotideFillProperty) as Brush;
                if (val != null)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFill;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideFillProperty, value); }
        }

        public char? NucleotideSubstitution
        {
            get
            {
                char? val = (char?)GetValue(NucleotideSubstitutionProperty);
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideSubstitution;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideSubstitutionProperty, value); }
        }

        public bool? IsNucleotideSubstituted
        {
            get
            {
                bool? val = (bool?)GetValue(IsNucleotideSubstitutedProperty);
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.IsNucleotideSubstituted;
                    else
                        return null;
                }
            }
            set { SetValue(IsNucleotideSubstitutedProperty, value); }
        }

        public double? NucleotideCircleDiameter
        {
            get
            {
                double? val = (double?)GetValue(NucleotideCircleDiameterProperty);
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleDiameter;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideCircleDiameterProperty, value); }
        }

        public double? NucleotideCircleEdgeThickness
        {
            get
            {
                double? val = (double?)GetValue(NucleotideCircleEdgeThicknessProperty);
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleEdgeThickness;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideCircleEdgeThicknessProperty, value); }
        }

        public bool? NucleotideCircleIsFilled
        {
            get
            {
                bool? val = (bool?)GetValue(NucleotideCircleIsFilledProperty);
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleIsFilled;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideCircleIsFilledProperty, value); }
        }

        public Brush NucleotideCircleFill
        {
            get
            {
                Brush val = (Brush)GetValue(NucleotideCircleFillProperty);
                if (val != null)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleFill;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideCircleFillProperty, value); }
        }

        public Brush NucleotideCircleStroke
        {
            get
            {
                Brush val = (Brush)GetValue(NucleotideCircleStrokeProperty);
                if (val != null)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleStroke;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideCircleStrokeProperty, value); }
        }

        public bool? BasePairConnectorIsFilled
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public bool? BasePairConnectorIsVisible
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public Brush BasePairConnectorFill
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public Brush BasePairConnectorStroke
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public double? BasePairConnectorStrokeThickness
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public double? BasePairConnectorSize
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public BasePairConnectorRenderingMode? BasePairConnectorRenderingStyle
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public void NotifyParentHierarchyChanged()
        {
            foreach (var child in ChildrenInternal)
                child.NotifyParentHierarchyChanged();
        }

        public void PropagateVisualPropertyChanged()
        {
            foreach (var child in ChildrenInternal)
                child.PropagateVisualPropertyChanged();
        }

        public void AddChild(IStructureElementVisualComponent component)
        {
            if (component != null && !ChildrenInternal.Contains(component))
            {
                ChildrenInternal.Add(component);
                component.ParentComponent = this;
            }
        }

        public void RemoveChild(IStructureElementVisualComponent component)
        {
            if (component != null && ChildrenInternal.Contains(component))
            {
                ChildrenInternal.Remove(component);
                component.ParentComponent = null;
            }
        }

        public bool HitTest(Point point, bool includeChildren)
        {
            if (includeChildren)
            {
                foreach (var child in ChildrenInternal)
                {
                    if (child.HitTest(point, includeChildren))
                        return true;
                }
            }

            return false;
        }

        public void MakeSelectable()
        {
            IsSelectable = true;
            foreach (var child in ChildrenInternal)
                child.MakeSelectable();
        }

        public void MakeUnselectable()
        {
            IsSelectable = false;
            foreach (var child in ChildrenInternal)
                child.MakeUnselectable();
        }

        public void Select(bool showNucleotideConnectors)
        {
            IsSelected = true;
            foreach (var child in ChildrenInternal)
                child.Select(showNucleotideConnectors);

            foreach (var contextuallyRelatedStrand in ContextuallyRelatedStrands)
                contextuallyRelatedStrand.ContextuallySelect();
        }

        public void Deselect()
        {
            IsSelected = false;
            foreach (var child in ChildrenInternal)
                child.Deselect();

            foreach (var contextuallyRelatedStrand in ContextuallyRelatedStrands)
                contextuallyRelatedStrand.ContextuallyDeselect();
        }

        public void ContextuallySelect()
        {
            foreach (var child in ChildrenInternal)
                child.ContextuallySelect();
        }

        public void ContextuallyDeselect()
        {
            foreach (var child in ChildrenInternal)
                child.ContextuallyDeselect();
        }

        public bool IsNucleotideFillInheriting()
        {
            var fill = GetValue(NucleotideFillProperty);
            return (fill == null) ? true : false;
        }

        public bool IsNucleotideCircleFillInheriting()
        {
            var fill = GetValue(NucleotideCircleFillProperty);
            return (fill == null) ? true : false;
        }

        public bool IsNucleotideCircleStrokeInheriting()
        {
            var stroke = GetValue(NucleotideCircleStrokeProperty);
            return (stroke == null) ? true : false;
        }

        public bool IsBasePairConnectorFillInheriting()
        {
            throw new NotSupportedException();
        }

        public bool IsBasePairConnectorStrokeInheriting()
        {
            throw new NotSupportedException();
        }

        public void StartEditTracking()
        {
            throw new NotImplementedException();
        }

        public void InterruptEditTracking()
        {
            throw new NotImplementedException();
        }

        public void UninterruptEditTracking()
        {
            throw new NotImplementedException();
        }

        public void ApplyPropertyEditSet(StructureElementPropertyChangeSet set, bool interruptEditTracking)
        {
            throw new NotImplementedException();
        }

        public void UndoAllPropertyChanges()
        {
            throw new NotImplementedException();
        }

        public void UndoLastPropertyChange()
        {
            throw new NotImplementedException();
        }

        public object SaveProperties()
        {
            throw new NotImplementedException();
        }

        public bool IsDirty()
        {
            throw new NotImplementedException();
        }

        #endregion

        public void AddContextuallyRelatedStrand(LoopStructureElementVisualComponent strand)
        {
            ContextuallyRelatedStrands.Add(strand);
            ContextMessage = BuildContextMessage();
        }

        public void LoopNucleotideMovedTo(int nucleotideIndex, Point location)
        {
            if (NucleotideIndices.Contains(nucleotideIndex))
            {
                RenderingArc.MoveNucleotideTo(nucleotideIndex, location);
                PropagateRenderingArcUpdates();
            }
            else
            {
                throw new ArgumentException(string.Format("Error: Failed Nucleotide move within loop because Nucleotide {0} is not within the loop {1}", nucleotideIndex, StartNucleotideIndex));
            }
        }

        public void LoopNucleotideMovedBy(int nucleotideIndex, Point delta)
        {
            if (NucleotideIndices.Contains(nucleotideIndex))
            {
                RenderingArc.MoveNucleotideBy(nucleotideIndex, delta);
                PropagateRenderingArcUpdates();
            }
            else
            {
                throw new ArgumentException(string.Format("Error: Failed Nucleotide move within loop because Nucleotide {0} is not within the loop {1}", nucleotideIndex, StartNucleotideIndex));
            }
        }

        public void MoveBy(Point delta)
        {
            RenderingArc.MoveBy(delta);
            PropagateRenderingArcUpdates();
        }

        public Dictionary<int, Point> Snap()
        {
            Dictionary<int, Point> arcUpdates = RenderingArc.Snap();
            PropagateRenderingArcUpdates();
            return arcUpdates;
        }

        public void ChangeLoopHeight(double desiredHeight)
        {
            RenderingArc.ChangeArcHeight(desiredHeight);
            PropagateRenderingArcUpdates();
        }

        public Dictionary<int, Point> ChangeLoopWidth(double desiredWidth)
        {
            Dictionary<int, Point> strandEndDelta = RenderingArc.ChangeArcWidth(desiredWidth);
            PropagateRenderingArcUpdates();
            return strandEndDelta;
        }

        public LoopStructureElementVisualComponent(StrandArc renderingArc, int startIndex, int endIndex, IEnumerable<IBioSymbol> strand,
            StructureExtentType strandType)
        {
            if (strandType == StructureExtentType.Helix || strandType == StructureExtentType.Knot)
                throw new ArgumentException(string.Format("Error: Loop extent cannot have extent type: {0}", strandType));

            if (endIndex < startIndex || (((endIndex - startIndex) + 1) != strand.Count()))
                throw new ArgumentException(string.Format("Error: Invalid strand or strand data did not match specfied length, start: {0} to end: {1}", startIndex, endIndex));

            StartNucleotideIndex = startIndex;
            EndNucleotideIndex = endIndex;
            Strand = strand;
            StrandType = strandType;
            ChildrenInternal = new List<IStructureElementVisualComponent>();
            ContextuallyRelatedStrands = new List<LoopStructureElementVisualComponent>();
            PhysicalLoopVisual = new LoopDrawingVisual(this);
            SetRenderingArc(renderingArc);
            Debug.Assert(NucleotideIndices.Count(idx => RenderingArc.AllLocations.ContainsKey(idx)) == NucleotideIndices.Count());
            Bounds = PhysicalLoopVisual.Measure();
            ContextMessage = BuildContextMessage();
        }

        private LoopDrawingVisual PhysicalLoopVisual { get; set; }
        private List<IStructureElementVisualComponent> ChildrenInternal { get; set; }
        private List<LoopStructureElementVisualComponent> ContextuallyRelatedStrands { get; set; }
        private StrandArc RenderingArc { get; set; }
        private bool _isSelected = false;
        private List<Point> _edges = new List<Point>();

        private bool _isSelectable = false;
        private bool IsSelectable
        {
            get { return _isSelectable; }
            set
            {
                bool newSelectableState = value;
                if (!newSelectableState && IsSelected)
                    IsSelected = false; //Clear the selection flag if selected
                _isSelectable = newSelectableState;
            }
        }

        private void PropagateRenderingArcUpdates()
        {
            Location = RenderingArc.StrandStartNucleotideLocation;
            ArcRadius = RenderingArc.StrandArcXRadius;
            ArcHeight = RenderingArc.StrandArcYRadius;
            Edges.Clear();
            Edges.Add(RenderingArc.StrandStartNucleotideLocation);
            Edges.Add(RenderingArc.StrandEndNucleotideLocation);
            Bounds = PhysicalLoopVisual.Measure();
            PhysicalLoopVisual.Render();
        }

        private void SetRenderingArc(StrandArc arc)
        {
            Debug.Assert(arc != null);
            RenderingArc = arc;
            Location = RenderingArc.StrandStartNucleotideLocation;
            ArcRadius = RenderingArc.StrandArcXRadius;
            ArcHeight = RenderingArc.StrandArcYRadius;
            Edges.Clear();
            Edges.Add(RenderingArc.StrandStartNucleotideLocation);
            Edges.Add(RenderingArc.StrandEndNucleotideLocation);
        }

        private string BuildContextMessage()
        {
            string retValue = null;
            if (StrandType == StructureExtentType.Hairpin)
                retValue = string.Format("Hairpin Loop of size {0} from {1} to {2}", Strand.Count(), StartNucleotideIndex + 1, EndNucleotideIndex + 1);
            else if(StrandType == StructureExtentType.Bulge)
                retValue = string.Format("Bulge Loop of size {0} from {1} to {2}", Strand.Count(), StartNucleotideIndex + 1, EndNucleotideIndex + 1);
            else if (StrandType == StructureExtentType.Internal)
            {
                if(ContextuallyRelatedStrands.Count == 1)
                {
                    retValue = string.Format("Internal Loop Strand of size {0} from {1} to {2} with related Strand of size {3} from {4} to {5}", Strand.Count(), 
                        StartNucleotideIndex + 1, EndNucleotideIndex + 1, ContextuallyRelatedStrands[0].Strand.Count(), ContextuallyRelatedStrands[0].StartNucleotideIndex, ContextuallyRelatedStrands[0].EndNucleotideIndex);
                }
                else
                {
                    retValue = string.Format("Internal Loop Strand of size {0} from {1} to {2}, but failed to identify the related Strand", Strand.Count(), 
                        StartNucleotideIndex + 1, EndNucleotideIndex + 1);
                }
            }
            else if (StrandType == StructureExtentType.Multistem)
            {
                if (ContextuallyRelatedStrands.Count > 0)
                {
                    string multistemloopctx = string.Format("Multistem Loop Strand of size {0} from {1} to {2} with {3} related Strands:", Strand.Count(), StartNucleotideIndex + 1,
                        EndNucleotideIndex + 1, ContextuallyRelatedStrands.Count);
                    foreach (var relatedstrand in ContextuallyRelatedStrands)
                        multistemloopctx = multistemloopctx + string.Format("\nStrand {0} to {1} of size {2}", relatedstrand.StartNucleotideIndex + 1, relatedstrand.EndNucleotideIndex + 1, relatedstrand.Strand.Count());
                    retValue = multistemloopctx;
                }
                else
                {
                    retValue = string.Format("Multistem Loop Strand of size {0} from {1} to {2}, but failed to identify the related Strands", Strand.Count(),
                        StartNucleotideIndex + 1, EndNucleotideIndex + 1);
                }
            }
            else if (StrandType == StructureExtentType.Free)
                retValue = string.Format("Free Loop of size {0} from {1} to {2}", Strand.Count(), StartNucleotideIndex + 1, EndNucleotideIndex + 1);
            else if (StrandType == StructureExtentType.Tail)
                retValue = string.Format("Tail Loop of size {0} from {1} to {2}", Strand.Count(), StartNucleotideIndex + 1, EndNucleotideIndex + 1);
            else
                retValue = string.Format("Unpaired Loop of size {0} from {1} to {2}", Strand.Count(), StartNucleotideIndex + 1, EndNucleotideIndex + 1);
            return retValue;
        }
    }
}
